# -*- coding: binary -*-

# Outputs Kubernetes API responses in table format
class Msf::Exploit::Remote::HTTP::Kubernetes::Output::Table

  # Creates a new `Msf::Exploit::Remote::HTTP::Kubernetes::Output::Table` instance
  #
  # @param [object] output The original output object with print_line/print_status/etc methods available.
  # @param [String] highlight_name_pattern The regex used to highlight noteworthy resource names
  def initialize(output, highlight_name_pattern: nil)
    @output = output
    @highlight_name_pattern = highlight_name_pattern
  end

  def print_error(*args)
    output.print_error(*args)
  end

  def print_good(*args)
    output.print_good(*args)
  end

  def print_status(*args)
    output.print_status(*args)
  end

  def print_enum_failure(resource, error)
    if error.is_a?(Msf::Exploit::Remote::HTTP::Kubernetes::Error::ApiError)
      print_status("#{resource} failure - #{error.message}")
    else
      output.print_error(error.message)
    end
  end

  def print_claims(claims)
    table = create_table(
      'Header' => 'Token Claims',
      'Columns' => ['name', 'value'],
      'Rows' => get_claim_as_rows(claims)
    )

    print_table(table)
  end

  def print_version(version)
    table = create_table(
      'Header' => 'Server API Version',
      'Columns' => ['name', 'value']
    )

    version.each_pair do |key, value|
      table << [
        key,
        value
      ]
    end

    print_table(table)
  end

  def print_namespaces(namespaces)
    table = create_table(
      'Header' => 'Namespaces',
      'Columns' => ['#', 'name']
    )

    namespaces.each.with_index do |item, i|
      table << [
        i,
        item.dig(:metadata, :name)
      ]
    end

    print_table(table)
  end

  # Print the auth rules returned from a kubernetes client in the same format
  # as `kubectl auth can-i --list --namespace default -v8`
  def print_auth(namespace, auth)
    auth_table = Msf::Exploit::Remote::HTTP::Kubernetes::AuthParser.new(auth).as_table

    table = create_table(
      'Header' => "Auth (namespace: #{namespace})",
      # The table rows will already be sorted, disable the default sorting logic
      'SortIndex' => -1,
      'Columns' => auth_table[:columns],
      'Rows' => auth_table[:rows]
    )

    print_table(table)
  end

  def print_pods(namespace, pods)
    table = create_table(
      'Header' => "Pods (namespace: #{namespace})",
      'Columns' => ['#', 'namespace', 'name', 'status', 'containers', 'ip']
    )

    pods.each.with_index do |item, i|
      containers = item.dig(:spec, :containers).map do |container|
        ports = container.fetch(:ports, []).map do |ports|
          "#{ports[:protocol]}:#{ports[:containerPort]}"
        end.uniq
        details = "image: #{container[:image]}"
        details << " #{ports.join(',')}" if ports.any?
        "#{container[:name]} (#{details})"
      end
      table << [
        i,
        namespace,
        item.dig(:metadata, :name),
        item.dig(:status, :phase),
        containers.join(', '),
        (item.dig(:status, :podIPs) || []).map { |ip| ip[:ip] }.join(',')
      ]
    end

    print_table(table)
  end

  def print_secrets(namespace, secrets)
    table = create_table(
      'Header' => "Secrets (namespace: #{namespace})",
      'Columns' => ['#', 'namespace', 'name', 'type', 'data', 'age']
    )

    secrets.each.with_index do |item, i|
      table << [
        i,
        namespace,
        item.dig(:metadata, :name),
        item[:type],
        item.fetch(:data, {}).keys.join(','),
        item.dig(:metadata, :creationTimestamp)
      ]
    end

    print_table(table)
  end

  protected

  attr_reader :highlight_name_pattern, :output

  def create_table(options)
    default_options = {
      'Indent' => indent_level,
      # For now, don't perform any word wrapping on the table as it breaks the workflow of
      # copying container/secret names
      'WordWrap' => false,
      'ColProps' => {
        'data' => {
          'Stylers' => [
            Msf::Ui::Console::TablePrint::HighlightSubstringStyler.new([highlight_name_pattern])
          ]
        },
        'name' => {
          'Stylers' => [
            Msf::Ui::Console::TablePrint::HighlightSubstringStyler.new([highlight_name_pattern])
          ]
        },
        'age' => {
          'Formatters' => [
            Msf::Ui::Console::TablePrint::AgeFormatter.new
          ]
        }
      }
    }

    Rex::Text::Table.new(default_options.merge(options))
  end

  def indent_level
    2
  end

  def with_indent(string, amount = indent_level)
    "#{' ' * amount}#{string}"
  end

  def print_table(table)
    output.print(table.to_s)
    output.print_line("#{' ' * indent_level}No rows") if table.rows.empty?
    output.print_line
  end

  def get_claim_as_rows(hash, parent_keys: [])
    return [] if hash.empty?

    result = []
    hash.each_pair do |key, value|
      if value.is_a?(Hash)
        result += get_claim_as_rows(value, parent_keys: parent_keys + [key])
      else
        result << [(parent_keys + [key.to_s]).join('.'), value]
      end
    end

    result
  end
end
